<?php

namespace App\Service;

class SnowflakeIdGenerator
{
    private const MAX_MACHINE_ID = (1 << 10) - 1;
    private const MAX_WORKER_ID = (1 << 5) - 1;
    private const SEQUENCE_MASK = (1 << 12) - 1;
    private const EPOCH = 1577836800000; // 2020-01-01T00:00:00Z

    private int $workerId;
    private int $sequence = 0;
    private ?int $lastTimestamp = null;

    public function __construct(int $workerId)
    {
        if ($workerId > self::MAX_WORKER_ID || $workerId < 0) {
            throw new \InvalidArgumentException("Worker Id cannot be greater than " . self::MAX_WORKER_ID);
        }
        $this->workerId = $workerId;
    }

    public function generate(): int
    {
        $timestamp = $this->getCurrentTimestamp();
        if ($timestamp < $this->lastTimestamp) {
            throw new \RuntimeException("Clock moved backwards. Refusing to generate id");
        }

        if ($this->lastTimestamp === $timestamp) {
            $this->sequence = ($this->sequence + 1) & self::SEQUENCE_MASK;
            if ($this->sequence === 0) {
                $timestamp = $this->waitForNextMillis($this->lastTimestamp);
            }
        } else {
            $this->sequence = 0;
        }

        $this->lastTimestamp = $timestamp;

        return ($timestamp - self::EPOCH) << 22 |
            ($this->workerId << 12) |
            $this->sequence;
    }

    private function getCurrentTimestamp(): int
    {
        return (int)(microtime(true) * 1000);
    }

    private function waitForNextMillis(int $lastTimestamp): int
    {
        $timestamp = $this->getCurrentTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getCurrentTimestamp();
        }
        return $timestamp;
    }
}